Understanding DynamicModule

Lou D’Andria, Wolfram Research

DynamicModule is one of the key technologies behind Manipulate, but there are few who understand it deeply. In this talk, we will explore the significant features of DynamicModule and DynamicModuleBox -- including those related to scoping, evaluation order, expression storage, and more -- and how to use them effectively.

Module is awesome

Key features:

Module implements lexical scoping — local variables can only be referenced from inside that Module.

Before evaluating its second argument, Module substitutes new symbols for each of the local variables.

Module[{var},var]

These renamed symbols have the Temporary attribute, so they can be automatically cleared and removed when that Module completes, usually.

Module[{x=1},x+=2;x+3]

?x$*

But Module is not ideal for interface scoping

Key limitations:

Module’s local variable naming only guarantees uniqueness within a single kernel session, not between sessions.

Module[{var},var]

Module variables are temporary, but interfaces need to be able to store their state, even across sessions.

Module scopes during the course of an evaluation, but interfaces need to be able to scope in a particular location on the screen, even when created via copy/paste.

Module[{x=0.5},{Slider[Dynamic[x]],Dynamic[x]}]

Enter DynamicModule

DynamicModule exhibits many of the same scoping behaviors as Module, plus a few extra features:

DynamicModule doesn't disappear after it evaluates.

Module[{x=1},x+=2;x+3]//InputForm

DynamicModule[{x=1},x+=2;x+3]//InputForm

DynamicModule stores all the definitions of its local variables by default.

DynamicModule[{x=1, f},f[t_] := t+1;f[x]]//InputForm

DynamicModule typesets as another scoping construct — DynamicModuleBox — where the details of variable renaming are handled by the front end at render time.

DynamicModule[{x=0.5},{Slider[Dynamic[x]],Dynamic[x]}]

DynamicModule allows you to run arbitrary code when that box is first displayed in a session.

DynamicModule[{x},{Slider[Dynamic[x]],Dynamic[x]},Initialization:>(x=1)]

DynamicModule evaluation, typesetting, rendering

There are interesting things to say about DynamicModule evaluation, DynamicMoulde typesetting (in the kernel), and DynamicModuleBox rendering (in the front end)

Evaluation

DynamicModule is HoldAll, but like many HoldAll functions, it does not simply hold its arguments. It performs some controlled evaluation.

When evaluating a DynamicModule, we go from this:

DynamicModule[{vars}, (expr1; expr2; expr3)]

to this:

DynamicModule[{newVars}, result,
DynamicModuleValues :> defs]

where newVars, newResult, and defs are the result of evaluating the original content within the scope of a Module with appropriate local variables.

DynamicModule[{x=123},x=456;x]//InputForm

Initialization

Notice that the Initialization option setting is *not* evaluated during the evaluation pass.

DynamicModule[{x=123},x=456;x,Initialization:>(x=789)]//InputForm

Typesetting

Typesetting transforms a DynamicModule to a DynamicModuleBox, to be displayed by the front end.

During typesetting, variables are renamed in such a way that the front end can control their subsequent scoping.

DynamicModule[{x=123},x=456;x]//InputForm

DynamicModule[{x=123},x=456;x]//ToBoxes//InputForm

SaveDefinitions

Typesetting is also when SaveDefinitions -> True becomes an explicit Initialization setting.

h[t_] := t+1;
dm=DynamicModule[{x=456},Dynamic[h[x]],SaveDefinitions->True];
dm//InputForm

ToBoxes[dm]//InputForm

Initialization

The Initialization option setting is *not* evaluated during the typesetting pass.

Rendering

The first time a particular DynamicModuleBox is displayed in a particular front end and kernel session, its Initialization is finally evaluated.

DynamicModule[{id},Dynamic[id],Initialization:>(id = $ProcessID)]

Like Dynamic, the Initialization will only run if the DynamicModuleBox is displayed on screen. If it’s in a closed group, or suppressed with a semi-colon, …, then it won’t.

Test your understanding (BONUS)

Here’s an example to see the order in which various sub-expressions get evaluated.

Before you evaluate it, read the code to determine which log messages come from which pieces of the DynamicModule. What order would you expect the log messages to arrive in? Which log messages would you expect to see if you wrapped the DynamicModule in InputForm before evaluating it? In Hold? In both Hold and InputForm?

start=AbsoluteTime[];
log[label_] := Echo[Row[{AbsoluteTime[]-start, " ", label}]]

DynamicModule[{var=log["initial variable value"]},
(
log["content"];
log["more content"];
Dynamic[Framed[log["dynamic content"]]]
),
Initialization:>log["initialization"]
]

Initialization and SynchronousInitialization

If SynchronousInitialization is True (the default), then the Initialization setting will be evaluated before any synchronous Dynamics in the body of the DynamicModule.

DynamicModule[{x=0},Dynamic[g[x]],Initialization:>(g[t_] := t+1)]

What about async inits?

If you have a long running Initialization, set SynchronousInitialization -> False, and make sure your Dynamics can handle displaying before the init is finished.

DynamicModule[{x},Dynamic[Panel[x]],Initialization:>(Pause[3];x="ready"),SynchronousInitialization->False]

DynamicModule[{x},Dynamic[Panel[x]],Initialization:>(x=Row[{"please wait",ProgressIndicator[Appearance -> "Ellipsis"]}];Pause[3];x="ready"),SynchronousInitialization->False]

Manipulate does this automatically, thus avoiding problems for dynamics in the content area:

Manipulate[g[x],{x,0,1},Initialization:>(Pause[3];g[t_] := t+1),SynchronousInitialization->False]

Initialization vs SaveDefinitions (BONUS)

If you set both SaveDefinitions -> True and a literal Initialization, the definitions slurped in by SaveDefinitions come first.

h[t_] := t+1;
dm=DynamicModule[{x=456},Dynamic[h[x]],SaveDefinitions->True,Initialization:>(x=789)];
dm//ToBoxes//InputForm

Common mistake: Initialization vs Scoping

Only variables mentioned in the DynamicModule's first argument are local to that DynamicModule.

Symbols are not localized just by virtue of being present in the Initialization.

That means you can build DynamicModules that set the same global symbol to a different global value:

DynamicModule[{},Dynamic[season[]],Initialization:>(season[] := "duck")]

DynamicModule[{},Dynamic[season[]],Initialization:>(season[]:="rabbit")]

?season

One fix

DynamicModule[{season},Dynamic[season[]],Initialization:>(season[]:="duck")]

DynamicModule[{season},Dynamic[season[]],Initialization:>(season[]:="rabbit")]

Another fix

Use the CellContext option to automatically use a unique context for each notebook or cell.

CellPrint[ExpressionCell[DynamicModule[{},Dynamic[season[]],Initialization:>(season[]:="duck")],"Output",CellContext -> Cell]]

CellPrint[ExpressionCell[DynamicModule[{},Dynamic[season[]],Initialization:>(season[]:="rabbit")],"Output",CellContext -> Cell]]

This is especially handy when building windowed interfaces.

Common mistake: Dynamic vs Module

Recall the syntax color warning:

Module[{x=0.5},{Slider[Dynamic[x]],Dynamic[x]}]

Module variables names are temporary, and are not reliable as persistent references.

Dynamic needs a persistent reference to a variable.

Thus: Dynamic should never depend on local Module variables.

Borrowing an example from comp.soft-sys.math.mathematica

These are ok:

Dynamic[Module[{a},a]]

Module[{a},...;Dynamic[(* expression NOT involving a *)]]

Module[{a},...;Manipulate[(* NOT involving a *)]]

These are not:

Module[{a},Dynamic[a]]

Module[{a},Manipulate[a x,{x,0,1}]]

The fix

If an output contains Dynamics that need to refer to localized variables, use DynamicModule.

DynamicModule[{x=0.5},{Slider[Dynamic[x]],Dynamic[x]}]

Common mistake: utilities vs lexical scoping

Locally scoped symbols are not the same as global symbols with the “same” name.

Manipulate

This anti-pattern shows up too many times to count:

f[x_] := a Sin[b x];
Manipulate[Plot[f[x],{x,0,10}],{a,1,5},{b,1,5}]

DynamicModule

f[x_] := a Sin[b x];
DynamicModule[{a=1,b=1},Plot[f[x],{x,0,10}]]

Module

f[x_] := a Sin[b x];
Module[{a=1,b=1},Plot[f[x],{x,0,10}]]

The fix

If you want utilities to be able to see the values of local variables, either put the utilities into the same local scope, or pass the local variable values as arguments.

f[x_,a_, b_] := a Sin[b x];
Module[{a=1,b=1},Plot[f[x,a,b],{x,0,10}]]

Common omission: not handling kernel quits

Windowed interfaces which are, by design, not completely self-reinitializing.

DynamicModule[{sessionid=$SessionID},Dynamic[If[sessionid===$SessionID,"OK",Row[{"Not OK…", Button["Refresh",sessionid=$SessionID]}]]]]

Quit

Used by message formatting features in version 11, and by internal development tools.

Common mistake: Saving variables (BONUS)

Say you have a flag to remember whether a bunch of stuff has been loaded.

DynamicModule[{x,y,z,loaded},Dynamic[something[]], Initialization:>(...; loaded = True; ...)]

Think about serialization. Do you want this variable to have the same value in the next session? Probably not. Yes, you could change the initialization to clear that variable as the firs thing it does, but if it’s an async init, that won’t quite do the right thing. Best to just not save the value in the first place

DynamicModule[{x,y,z,loaded},Dynamic[something[]], Initialization:>(...; loaded = True; ...), UnsavedVariables:>{loaded}]

Front End ownership of variables (BONUS)

Where are the variables, really? Squirreled away in the FE` context.

DynamicModule[{asdf=0},Dynamic[Zeta[asdf]]]

??*`*asdf*

But don’t even try to set this value directly, as the front end will probably thwart your efforts. If you need to use to a DynamicModule variable outside of its rectangle on screen, use a wormhole.

Wormholes (BONUS)

If you want the same set of local variables shared between two or more rectangles on screen, you’ll need one main scope, and the others will inherit from it.

DynamicModule[{x},{
LabeledSlider[Dynamic[x]],
Button["print",Print @ DynamicModule[{},LabeledSlider[Dynamic[x]],InheritScope->True]]
}]

Key wormhole features

Things to notice:
• The new scope is being created by a control inside the main DynamicModuleBox. Common functions to create the wormhole are Print, CreateDialog, etc.
• The scope is created by an existing displayed DynamicModuleBox, and inherited by another DynamicModuleBox as it is created. You currently can’t build a wormhole between two existing DynamicModuleBoxes.
• There can be multiple wormholes attached to a single “parent” DynamicModuleBox.
• Wormholes do not persist past the current session.

And you can chain wormholes without incident:

f[Dynamic[var_]] := {
LabeledSlider[Dynamic[var]],
Button["print",Print @ DynamicModule[{},f[Dynamic[var]],InheritScope->True]]}

DynamicModule[{x=1},f[Dynamic[x]]]

Mathematica usage

Used by the assumptions dialog you get by clicking the “...” icon in control-equal.

LinguisticAssistant

Questions?